#include "VTTMemoryManager.h"

#include <windows.h>
#include <stdio.h>

using std::list;

// #include <iostream>
// 
// using std::cout;
// using std::endl;

using std::max;

VTTMemoryManager *VTTMemoryManager::memoryManager = 0;//new VTTMemoryManager();

/**
  *	Returns the total number of available bytes in the physical memory
  *
  * @return number of available bytes in the physical memory
  */
ULONG64 GetTotalSystemMemory()
{
	MEMORYSTATUSEX statex;

	statex.dwLength = sizeof (statex);

	GlobalMemoryStatusEx (&statex);

	return static_cast<ULONG64>( statex.ullAvailPhys );
}

/**
  *	Constructs a new VTTMemoryManager object. The maximum number of available
  * of physical memory and the goal for a cleanup operation are evaluated.
  */
VTTMemoryManager::VTTMemoryManager( void )
{
	mBytesUsedByObjects = 0;
	mMaximumBytesUsedByObjects = static_cast<ULONG64>( GetTotalSystemMemory() * 0.75 );
	//cout << "INFO: Application will use at most " << (mMaximumBytesUsedByObjects / 1024.0f) / 1024.0f << " MB of memory" << endl;
	mLowerBoundBytes = static_cast<ULONG64>( (mMaximumBytesUsedByObjects) * 2.0 / 3.0 );
	//cout << "Lower Bound: " << mLowerBoundBytes << endl;
}

/**
  *	Destroys the object
  */
VTTMemoryManager::~VTTMemoryManager( void )
{

}

/**
  *	Frees the memory in all the registered objects that is allocated on the heap
  */
void VTTMemoryManager::FreeAllMemory( void )
{
	list<VTTMemoryAllocator *>::iterator obj_it;

	for (obj_it = mRegisteredObjects.begin(); obj_it != mRegisteredObjects.end();)
	{
		(*obj_it)->FreeAllocatedMemory();
		obj_it = mRegisteredObjects.erase( obj_it );
	}
}

/**
  *	Returns a pointer to the one and only instance of VTTMemoryManager
  *
  * @return pointer to the one and only instance of VTTMemoryManager
  */
VTTMemoryManager *VTTMemoryManager::Get( void )
{
	if (!memoryManager)
		memoryManager = new VTTMemoryManager();

	return memoryManager;
}

/**
  *	Returns the number of available bytes in the physical memory
  *
  * @return number of available bytes in the physical memory
  */
ULONG64 VTTMemoryManager::GetFreePhysicalMemory( void )
{
	return GetTotalSystemMemory();
}

/**
  *	Returns the percentage of the used physical memory
  *
  * @return percentage of the used physical memory
  */
UINT32 VTTMemoryManager::GetPhysicalMemoryUsedPercentage( void )
{
	MEMORYSTATUSEX statex;
	statex.dwLength = sizeof (statex);
	GlobalMemoryStatusEx (&statex);

	return static_cast<UINT32>( statex.dwMemoryLoad );
}

/**
  *	Registers an VTTMemoryAllocator object with its number of allocated bytes
  *
  * @param object memory allocator object
  * @param bytesAllocated number of allocated bytes
  */
void VTTMemoryManager::Register( VTTMemoryAllocator *object, UINT32 bytesAllocated )
{
	mBytesUsedByObjects += bytesAllocated;

	//cout << "Memory allocated: " << (static_cast<double>( mBytesUsedByObjects ) / 1024.0) / 1024.0 << " MB " << endl;

	if (mBytesUsedByObjects > mMaximumBytesUsedByObjects)
	{
		//RequestMemory( static_cast<UINT32>( max( mBytesUsedByObjects / 2, mBytesUsedByObjects - mMaximumBytesUsedByObjects ) ) );

		RequestMemory( static_cast<UINT32>( mBytesUsedByObjects - mLowerBoundBytes) );
	}

	mRegisteredObjects.push_back( object );

	//cout << "Physical Memory Used: " << GetPhysicalMemoryUsedPercentage() << "%" << endl;
}

/**
  *	Tries to free at least bytesNeeded bytes in memory
  *
  * @param bytesNeeded number of bytes needed
  * @return boolean value indicating whether at least bytesNeeded bytes could be freed or not
  */
bool VTTMemoryManager::RequestMemory( UINT32 bytesNeeded )
{
	list<VTTMemoryAllocator *>::iterator obj_it;

	UINT32 memory_freed_by_obj = 0;
	UINT32 memory_freed = 0;

	for (obj_it = mRegisteredObjects.begin(); obj_it != mRegisteredObjects.end();)
	{
		memory_freed_by_obj = (*obj_it)->FreeAllocatedMemory();
		memory_freed += memory_freed_by_obj;
		mBytesUsedByObjects -= memory_freed_by_obj;

		obj_it = mRegisteredObjects.erase( obj_it );

		if (memory_freed >= bytesNeeded)
			return true;
	}

	return false;
}